﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using ClassLibrary.SignalType;

namespace ClassLibrary.Communication
{
    /// <summary>
    /// @Author Marcin Król
    /// uses sockets to send and receive data
    /// used to get objects of Port class, simulates separate communication of each Port
    /// </summary>
    public class CommunicationService
    {
        // --- private implementation of Port class
        private class _Port : Port
        {
            private readonly int _id;
            private Data _dataBuffer;
            
            public event HandleSimulationEvents DataReceived;

            public _Port(int id)
            {
                _id = id;
            }

            public int Id
            {
                get { return _id; }
            }

            /// <summary>
            /// Adds ports id to data to be sent
            /// </summary>
            /// <param name="data"></param>
            public void Send(Data data)
            {
                var indexedData = new IndexedData(data, _id);
                communicationServiceInstance.Send(indexedData);
            }

            /// <summary>
            /// Read received data
            /// </summary>
            /// <returns></returns>
            public Data Read()
            {
                return _dataBuffer;
            }

            /// <summary>
            /// Invokes the data received event and moves received data to buffer of this port
            /// </summary>
            /// <param name="data"></param>
            public void CallDataReceivedEvent(Data data)
            {
                _dataBuffer = data;
                DataReceived(this, EventType.DataReceived); 
            }
        }
        // ---

        // --- CommunicationService implementation
        private static CommunicationService communicationServiceInstance = new CommunicationService();

        _Port[] _ports;

        // --- socket fields
        readonly byte[] _mDataBuffer = new byte[8000];
        public Socket MSocClient;
        // ---

        private CommunicationService(){}

        public static CommunicationService GetInstance()
        {
            return communicationServiceInstance;
        }

        /// <summary>
        /// Sends indexed data using binary serialization
        /// </summary>
        /// <param name="indexedData"></param>
        private void Send(IndexedData indexedData)
        {
            var binaryFormatter = new BinaryFormatter();
            var memoryStream = new MemoryStream();
            binaryFormatter.Serialize(memoryStream, indexedData);
            var serializedData = memoryStream.ToArray();
            
            MSocClient.BeginSend(serializedData, 0, serializedData.Length, SocketFlags.None, null, null);
        }


        /// <summary>
        /// creates given number of ports
        /// </summary>
        /// <param name="portsNumber"></param>
        /// <returns></returns>
        public Port[] SetupPorts(int portsNumber)
        {
            _ports = new _Port[portsNumber];
            for (int portId = 0; portId < portsNumber; portId++)
            {
                _ports[portId] = new _Port(portId);
            }
            return _ports;
        }

        /// <summary>
        /// make connection with the wire cloud
        /// </summary>
        /// <param name="strIpAddress"></param>
        /// <param name="portNo"></param>
        public void Connect(String strIpAddress, int portNo)
        {
            MSocClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // get the remote IP address...
            var ipAddress = IPAddress.Parse(strIpAddress);
            //create the end point
            var ipEndPoint = new IPEndPoint(ipAddress, portNo);
            //connect to the remote host...
            MSocClient.Connect(ipEndPoint);
            //watch for data ( asynchronously )...
            WaitForData();
        }

        /// <summary>
        /// Login as Id to the wire cloud
        /// </summary>
        /// <param name="id"></param>
        public void Login(String id)
        {
            byte[] byData = System.Text.Encoding.ASCII.GetBytes(id);
            MSocClient.BeginSend(byData, 0, byData.Length, SocketFlags.None, null, null);
        }

        /// <summary>
        /// Waits for incoming data
        /// </summary>
        private void WaitForData()
        {
            // now start to listen for any data...
             MSocClient.BeginReceive(_mDataBuffer, 0, _mDataBuffer.Length, SocketFlags.None, OnDataReceived, null);
        }

        /// <summary>
        /// Receives, extracts data and indicates the port on which it was received
        /// </summary>
        /// <param name="asyn"></param>
        private void OnDataReceived(IAsyncResult asyn)
        {
            //end receive...
            var bytesReceivedCount = MSocClient.EndReceive(asyn);
            var binaryFormatter = new BinaryFormatter();
            var memoryStream = new MemoryStream(_mDataBuffer, 0, bytesReceivedCount);
            var indexedData = (IndexedData) binaryFormatter.Deserialize(memoryStream);
          
            var portId = indexedData.PortId;
            var data = indexedData.Data;
            _ports[portId].CallDataReceivedEvent(data);            
            WaitForData();
        }
    }
}
